/*
 * usart.c
 *
 * Copyright 2011 Pieter Agten
 *
 * This file is part of Yarf.
 *
 * Yarf is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Yarf is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Yarf.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   usart.c
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   18 sep 2011
 * @brief  Implements a driver for USART0, which is connected to the host.
 * 
 */

#include "usart.h"

#include "yarf.h"
#include "fastio.h"

#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <avr/interrupt.h>

/**
 * The baud rate, which must be defined before loading the <util/setbaud.h>
 * helper macros.
 *
 * @see config.h
 */
#define BAUD HOST_BAUD_RATE
#include <util/setbaud.h>

/**
 * A character which can be inserted into the input buffer to signal the
 * occurrence of an error while receiving a character. This can be any
 * non-ASCII character.
 */ 
#define ERROR_CHARACTER  ((char)0xFE)


/**
 * Array for buffering incoming characters. This array is used as a circular
 * buffer.
 */
static volatile char rx_buffer[USART0_RECEIVE_BUFFER_SIZE];
/**
 * The index of the head of the input buffer. This is where new characters will
 * be inserted.
 */
static volatile uint8_t rxbuf_head;
/**
 * The index of the tail of the input buffer. This is where the next character
 * will be fetched from, when reading from the input.
 */
static volatile uint8_t rxbuf_tail;

/**
 * Returns the successor index of the given index in the input buffer.
 * 
 * @param  index the index of which to return the successor
 * @return the successor index of the given index in the input buffer
 */
static inline uint8_t
buf_index_next(uint8_t index)
{
  index += 1;
  if (index == USART0_RECEIVE_BUFFER_SIZE) {
    index = 0;
  }
  return index;
}

/**
 * Returns the predecessor index of the given index in the input buffer.
 *
 * @param  index the index of which to return the predecessor
 * @return the predecessor index of the given index in the input buffer
 */
static inline uint8_t
buf_index_prev(uint8_t index)
{
  if (index == 0) {
    return USART0_RECEIVE_BUFFER_SIZE - 1;
  }

  return index - 1;
}

/**
 * Returns whether the input buffer is full.
 *
 * @return whether the input buffer is full
 */
static inline bool
buf_full(void)
{
  return buf_index_next(rxbuf_head) == rxbuf_tail;
}

/**
 * Returns whether the input buffer is empty.
 *
 * @return whether the input buffer is empty
 */
static inline bool
buf_empty(void)
{
  return rxbuf_head == rxbuf_tail;
}

/**
 * Initializes the USART0 hardware.
 */
static inline void
usart0_init(void)
{
   /* Set baud rate using the helper macros */
   UBRR0H = UBRRH_VALUE;
   UBRR0L = UBRRL_VALUE;

#if USE_2X
   UCSR0A |= (1 << U2X0);
#else
   UCSR0A &= ~(1 << U2X0);
#endif

  /* Set frame format to 8N1 */
  UCSR0C = (3 << UCSZ00);

  /* Enable USART0 receiver and transmitter and the receive interrupt flag */
  UCSR0B = _BV(TXEN0) | _BV(RXEN0) | _BV(RXCIE0);  

  //TODO: clear serial buffer?
}




/**
 * Sends a character to the USART hardware buffer and waits until it has been 
 * transmitted.
 *
 * @param c       the character to transmit
 * @param stream  a reference to the underlying stream object from which the
 *                character is sent
 * @return 0 on success and a nonzero value if the character could not be 
 *         transmitted
 */
static int
usart0_putchar(char c, FILE *stream)
{
  if (c == '\n') {
    /* Add carriage return before line ending */
    usart0_putchar('\r', stream);
  }

  /* Wait for empty transmit buffer */
  loop_until_bit_is_set(UCSR0A, UDRE0);

  /* Put character into buffer, sends the data */
  UDR0 = c;

  return 0;
}


/**
 * Returns the first unread character from the USART.
 * 
 * @param stream  a reference to the stream to which the character will be 
 *                written
 * @return the first unread character from the USART or _FDEV_ERR if there was
 *         a problem reading the character
 *
 * The actual reception is line buffered, and one character is returned from
 * the input buffer at each invokation of this function. If the buffer is
 * empty, this function will wait for a new character to arrive. If there was a
 * problem reading the character to be returned, the function returns the
 * _FDEV_ERR error character.
 */
static int
usart0_getchar(FILE *stream)
{
  while (buf_empty()) {
    /* Wait for data to arrive in the buffer */
  }

  /* Get and return received data from buffer */
  int c = (int)rx_buffer[rxbuf_tail];
  rxbuf_tail = buf_index_next(rxbuf_tail);
  if (c == ERROR_CHARACTER) {
    c = _FDEV_ERR;
  }
  return c;
}


/**
 * Reads a character from the USART hardware buffer and appends it to the input
 * buffer.
 *
 * If the hardware buffer is full or an error occurred while reading or
 * receiving the character, a special ERROR_CHARACTER is written to the input
 * buffer at the position of the last character previously received.
 */
static inline void
read_character(void)
{
  if (buf_full() || UCSR0A & (_BV(FE0) | _BV(DOR0) | _BV(UPE0))) {
    /* An error has occurred. Insert a special error character into this line
     * so the parser will mark it as erroneous. */
    uint8_t last_character_received_index = buf_index_prev(rxbuf_head); //TODO: what if error on first char?
    rx_buffer[last_character_received_index] = ERROR_CHARACTER;

    /* Read character just to remove it from the hardware FIFO buffer */
    UDR0;
    return;
  }

  rx_buffer[rxbuf_head] = UDR0;
  rxbuf_head = buf_index_next(rxbuf_head);
}

/**
 * Interrupt service routine which is called whenever a new character has
 * arrived in the USART hardware buffer.
 */
ISR(USART0_RX_vect,ISR_BLOCK)
{
  read_character();
}


void
usart_init(void)
{
  usart0_init();
  rxbuf_head = 0;
  rxbuf_tail = 0;

  stdout = stdin = stderr = &usart0_stream;
}


void
usart0_try_read_character(void)
{
  if (UCSR0A & _BV(RXC0)) {
    read_character();
  }
}


FILE usart0_stream = FDEV_SETUP_STREAM(usart0_putchar, usart0_getchar, _FDEV_SETUP_RW);


